(*  :Title:	Signal Processing Systems  *)

(*  :Authors:	Brian Evans, James McClellan  *)

(*  :Summary:	To provide system properties and definitions a la Myers.  *)

(*  :Context:	SignalProcessing`ObjectOriented`System`  *)

(*  :PackageVersion:  2.7	*)

(*
    :Copyright:	Copyright 1989-1991 by Brian L. Evans
		Georgia Tech Research Corporation

	Permission to use, copy, modify, and distribute this software
	and its documentation for any purpose and without fee is
	hereby granted, provided that the above copyright notice
	appear in all copies and that both that copyright notice and
	this permission notice appear in supporting documentation,
	and that the name of the Georgia Tech Research Corporation,
	Georgia Tech, or Georgia Institute of Technology not be used
	in advertising or publicity pertaining to distribution of the
	software without specific, written prior permission.  Georgia
	Tech makes no representations about the suitability of this
	software for any purpose.  It is provided "as is" without
	express or implied warranty.
 *)

(*  :History:	*)

(*  :Keywords:	*)

(*
    :Source:	Myers, Cory.  {Signal Representation for Symbolic and
		  Numeric Processing}.  M.I.T. Ph. D. Thesis.  August,
		  1986.  Appendix D.

		Covell, Michele.  {An Algorithm Design Environment for
		  Signal Processing}.  M.I.T. Ph. D. Thesis.  December,
		  1989.
 *)

(*  :Warning:	*)

(*  :Mathematica Version:  1.2 or 2.0  *)

(*  :Limitation:  *)

(*
    :Discussion:  Systems are defined in the same format as are mathematical
		  operators.  This format was chosen so that Mathematica's
		  powerful pattern matcher could be used without adjustment.
		  A full blown object representation was not needed since
		  no cacheing, fetching, dynamic properties, etc. are needed
		  as they are for signals.  That is, a z-transform operator
		  always exhibits linearity, and its properties do not
		  change.  In addition, systems are not instantiated as
		  are signals.

		  Properties are attached to the operator AND its parameters.
		  If an operator can take optional arguments, then all flavors
		  of it must be separately installed by DefineSystem.
 *)

(*
    :Functions:	ASSOCIATIVE
		ADDITIVE
		COMMUTATIVE
		CONTINUOUS
		DefineSystem
		DISCRETE
		HOMOGENEOUS
		LINEAR
		LINEARPHASE
		MEMORYLESS
		MoveOperatorsToFront
		SEPARABLE
		SHIFTINVARIANT
		SystemQ
		ValidOperatorQ
 *)



(*  B E G I N     P A C K A G E  *)

BeginPackage[ "SignalProcessing`ObjectOriented`System`",
	      "SignalProcessing`Support`FilterSupport`",
	      "SignalProcessing`Support`SigProc`",
	      "SignalProcessing`Support`SupCode`" ]


If [ TrueQ[ $VersionNumber >= 2.0 ],
     Off[ General::spell ];
     Off[ General::spell1 ] ];


(*  U S A G E     I N F O R M A T I O N  *)

ASSOCIATIVE::usage =
	"ASSOCIATIVE is a system property. \
	It also serves as a function. \
	ASSOCIATIVE[system] returns True if the system is associative. \
	Note that a system is a (parameterized) operator to which \
	DefineSystem has attached properties. \
	See also DefineSystem and SystemQ."

ADDITIVE::usage =
	"ADDITIVE is a system property. \
	An operator T is additive if T[x + y] = T[x] + T[y] for all x, y. \
	It also serves as a function. \
	ADDITIVE[system] returns True if the operator system is additive. \
	Note that a system is a (parameterized) operator to which \
	DefineSystem has attached properties. \
	See also DefineSystem and SystemQ."

COMMUTATIVE::usage =
	"COMMUTATIVE is a system property. \
	It also serves as a function. \
	COMMUTATIVE[system] returns True if the system is commutative. \
	Note that a system is a (parameterized) operator to which \
	DefineSystem has attached properties. \
	See also DefineSystem and SystemQ."

CONTINUOUS::usage =
	"CONTINUOUS is a system property. \
	It also serves as a function. \
	CONTINUOUS[system] returns True if the operator system only takes \
	CONTINUOUS systems as arguments. \
	Note that a system is a (parameterized) operator to which \
	DefineSystem has attached properties. \
	See also DefineSystem and SystemQ."

DefineSystem::usage =
	"DefineSystem[operator, property1, property2, ...] will install \
	the operator as a system. \
	The properties property1, property2, ... are considered to be True \
	for the operator. \
	For example, if DefineSystem[Upsample[l, n], DISCRETE, LINEAR] \
	is evaluated, Upsample will be added to the list of system operators \
	SPsystems, and one of the four rules generated is \
	CONTINUOUS[Shift[l,n]] := True. \
	Note that CONTINUOUS[Shift] will have no meaning."

DISCRETE::usage =
	"DISCRETE is a system property. \
	It also serves as a function. \
	DISCRETE[system] returns True if the system only accepts \
	DISCRETE signals as arguments. \
	Note that a system is a (parameterized) operator to which \
	DefineSystem has attached properties. \
	See also DefineSystem and SystemQ."

HOMOGENEOUS::usage =
	"HOMOGENEOUS is a system property. \
	An operator T is homogeneous if T[a x] = a T[x] if a is a scalar. \
	It also serves as a function. \
	HOMOGENEOUS[system] returns True if the system is homogeneous. \
	Note that a system is a (parameterized) operator to which \
	DefineSystem has attached properties. \
	See also DefineSystem and SystemQ."

LINEAR::usage =
	"LINEAR is a system property. \
	A system is linear if it is both HOMOGENEOUS and ADDITIVE. \
	It also serves as a function. \
	LINEAR[system] returns True if the operator system is linear. \
	Note that a system is a (parameterized) operator to which \
	DefineSystem has attached properties. \
	See also DefineSystem and SystemQ."

LINEARPHASE::usage =
	"LINEARPHASE is a system property. \
	A system exhibits linear phase if the phase component of its \
	frequency response is an affine function of the frequency. \
	It also serves a a function. \
	LINEARPHASE[system] returns True if the system is memoryless. \
	Note that a system is a (parameterized) operator to which \
	DefineSystem has attached properties. \
	See also DefineSystem and SystemQ."

MEMORYLESS::usage =
	"MEMORYLESS is a system property. \
	A system is memoryless if each output corresponding to a given input \
	only depends on the input (and not past values of the input). \
	It also serves a a function. \
	MEMORYLESS[system] returns True if the system is memoryless. \
	Note that a system is a (parameterized) operator to which \
	DefineSystem has attached properties. \
	A MEMORYLESS system with a single input is also SHIFTINVARIANT. \
	See also DefineSystem and SystemQ."

MoveOperatorsToFront::usage =
	"MoveOperatorsToFront[head_operator, signals, signals_to_move, \
	operator_to_move] will move all operator_to_move operators \
	contained in signals_to_move to the front of the expression formed \
	by the head_operator operating on signals. \
	For example, MoveOperatorsToFront[ Convolve[n], \
	{ x[n], Shift[2,n][y[n]] }, { Shift[2,n][y[n]] }, Shift ] \
	yields Shift[2,n] [ Convolve[n] [ x[n], y[n] ] ]. \
	This is a valid rewriting of the original expression. \
	In this case, Convolve[n] [x[n], Shift[2,n][y[n]]]. \
	This object is used in the system rewrite rules to rewrite \
	properly an operator operating on more than one signal."

SEPARABLE::usage =
	"SEPARABLE is a system property. \
	It describes multidimensional systems that can decomposed \
	into a cascade of one-dimensional operations. \
	It also serves as a function. \
	SEPARABLE[system] returns True if the \
	operator system is separable. \
	Note that a system is a (parameterized) operator to which \
	DefineSystem has attached properties. \
	See also DefineSystem and SystemQ."

SHIFTINVARIANT::usage =
	"SHIFTINVARIANT is a system property. \
	It means that a shift in the input causes the same shift \
	in the output. \
	It also serves as a function. \
	SHIFTINVARIANT[system] returns True if the \
	operator system is shift-invariant. \
	Note that a system is a (parameterized) operator to which \
	DefineSystem has attached properties. \
	See also DefineSystem and SystemQ."

SystemProperties::usage =
	"SystemProperties[system] will show the properties attached to \
	of an operator or list of operators. \
	Note that a system is a (parameterized) operator to which \
	DefineSystem has attached properties. \
	See also DefineSystem and SystemQ."

SPsystems::usage =
	"SPsystems is a list of all recognized systems. \
	Note that a system is a (parameterized) operator to which \
	DefineSystem has attached properties. \
	New ones can be defined by using DefineSystem."

SPproperties::usage =
	"SPproperties contains a list of currently recognized \
	system properties like LINEAR."

SystemQ::usage =
	"SystemQ[system] will give True if system is in the SPsystems list, \
	and False otherwise. \
	Note that a system is a (parameterized) operator to which \
	DefineSystem has attached properties. \
	For example, by default, Shift[L, n] has been installed as a system, \
	but Shift without the parameters has not."

ValidOperatorQ::usage =
	"ValidOperatorQ[system, signal1, signal2, ...] will return True \
	if every signal can be processed by the system. \
	For example, False would be returned if system was only a \
	discrete system and signal1 was a continuous signal."

(*  E N D     U S A G E     I N F O R M A T I O N  *)


Begin[ "`Private`" ]


(*  G L O B A L S  *)

SPsystems = {}

ValidOperatorQ::incompatible =
  "The operator `` could not operate on one or more of these signals:  ``."

compatibilitycheck =
  "`1`/: `2`[inputs__] := \
	 Message[ValidOperatorQ::incompatible, `3`, {inputs}] /; \
	 ! ValidOperatorQ[`3`, inputs]"

SPproperties =
	{ASSOCIATIVE, ADDITIVE, COMMUTATIVE, CONTINUOUS, DISCRETE,
	 HOMOGENEOUS, LINEAR, LINEARPHASE, MEMORYLESS, SEPARABLE,
	 SHIFTINVARIANT}


(*  S U P P O R T I N G     R O U T I N E S  *)

(*  DefineSystem --  generates Mathematica code for a system description     *)
(*  abbreviations:  op for operator, prop for property, tsd for TagSetDelay  *)

(*
defproperty[prop_, op_, headop_] :=
	If [ AtomQ[op],
	     TagSetDelayed[op, prop[op], True],
	     TagSetDelayed[headop, prop[op], True];
		TagSetDelayed[headop, prop[headop], True] ]
 *)

(*  defproperty-- do not attach rules to Mathematica's built-in operators  *)
defproperty[prop_Symbol, headop_, headop_] :=
	Set[prop[headop], True] /;
	SameQ[Context[headop], "System`"]
defproperty[prop_Symbol, op_, headop_] :=
	TagSetDelayed[prop, prop[op], True] /;
	SameQ[Context[headop], "System`"]
defproperty[prop_Symbol, op_, headop_] :=
	TagSetDelayed[headop, prop[op], True]

SetAttributes[DefineSystem, HoldFirst]

DefineSystem[h_Symbol, properties__] :=
	definesystem[ h, h, h, h, properties ]
DefineSystem[h_[args__], properties__] :=
	definesystem[ h,
		      Apply[h, Map[ToString, {args}]],
		      Apply[h, Map[GeneratePattern, {args}]],
		      h[args],
		      properties ]

definesystem[headop_, opstr_, op_, fullop_, properties__] :=
	Block [	{proplist, protected, tsdattributes},

		protected = HasAttributes[headop, Protected];
		If [ protected, UnprotectIt[headop] ];
		proplist = ToList[properties];

		(*    Defining rules for compatibility checking between	*)
		(*  signals and systems. This is only necessary when an	*)
		(*  operator takes only continuous signals or discrete	*)
		(*  signals as arguments.				*)
		If [ Xor[MemberQ[proplist, DISCRETE],
			 MemberQ[proplist, CONTINUOUS]],
		     GenerateCode[
			StringForm[compatibilitycheck, headop,
				   InputForm[op], InputForm[fullop]] ] ];

		(*    TagSetDelayed needs to evaluate its arguments *)
		(*  for the code generation to work.		    *)
		tsdattributes = Attributes[TagSetDelayed];
		Unprotect[TagSetDelayed];
		ClearAttributes[TagSetDelayed, {HoldFirst, HoldRest, HoldAll}];

		(*    Establishes a property as an Mathematica operator  *)
		(*  which takes a system operator as an argument.	 *)
		(*  This allows statements like DISCRETE[Upsample] and   *)
		(*  DISCRETE[Upsample[3,n]] to return True.		 *)
		If [ MemberQ[proplist, LINEAR],
		     Combine[proplist, {ADDITIVE, HOMOGENEOUS}] ];
		Map [ defproperty[#1, op, headop]&, proplist ];

		(*  Return TagSetDelayed to its original evaluation settings  *)
		SetAttributes[TagSetDelayed, tsdattributes];

		(*  Maintain a list of DSP operators  *)
		Unprotect[SPsystems];
		Combine[SPsystems, {opstr}];
		Protect[SPsystems];

		(*  Restore protection of the operator if set beforehand  *)
		If [ protected, ProtectIt[headop] ];

		headop ]

(*  MoveOperatorToFront  *)
MoveOperatorsToFront[ op_, siglist_, sigleftlist_, optomove_ ] :=
	Block [	{numops, opindex, oplist, result},
		oplist = Map[OperatorInOperExpr, sigleftlist];
		numops = Length[oplist];
		result = Apply[op, Map[SignalsInOperExpr[#, optomove]&, siglist]];
		For [ opindex = 1, opindex <= numops, opindex++,
		      result = oplist[[opindex]] [ result ] ];
		result ]

(*  SystemQ  *)
SystemQ[op_[args__]] := SystemQ[op]
SystemQ[op_] := MemberQ[SPsystems, op] /; AtomQ[op]

(*  ValidOperatorQ  *)
ValidOperatorQ[system_, signal_] :=
	Which [ TrueQ[ DISCRETE[signal] ],
		  TrueQ[ DISCRETE[system] ],
		TrueQ[ CONTINUOUS[signal] ],
		  TrueQ[ CONTINUOUS[system] ],
		True,
		  True ]
ValidOperatorQ[system_, signal1_, signal__] :=
	And[ValidOperatorQ[system, signal1], ValidOperatorQ[system, signal]]


(*  R E C O G N I Z E D     S Y S T E M S  *)

(*  Standard Mathematica operators  *)

DefineSystem[Plus, COMMUTATIVE, CONTINUOUS, DISCRETE, MEMORYLESS]
DefineSystem[Times, COMMUTATIVE, CONTINUOUS, DISCRETE, LINEAR, MEMORYLESS]

(*  Operators in the signal processing packages  *)

DefineSystem[CircularShift[n0, N, n], DISCRETE, LINEAR, SHIFTINVARIANT]
DefineSystem[Conjugate,
	     ADDITIVE, CONTINUOUS, DISCRETE, MEMORYLESS, SHIFTINVARIANT]
DefineSystem[CConvolve[t],
	     ASSOCIATIVE, COMMUTATIVE, CONTINUOUS, LINEAR, SHIFTINVARIANT]
DefineSystem[Convolve[n],
	     ASSOCIATIVE, COMMUTATIVE, DISCRETE, LINEAR, SHIFTINVARIANT]
DefineSystem[Difference[k, n], DISCRETE, LINEAR, SEPARABLE]
DefineSystem[Divide, CONTINUOUS, DISCRETE, MEMORYLESS]
DefineSystem[Downsample[m, n], DISCRETE, LINEAR ]
Downsample/: SEPARABLE[ Downsample[m_, n_List] ] := DiagonalMatrixQ[m]
DefineSystem[DFT[l, n, k], DISCRETE, LINEAR]
DefineSystem[DTFT[n, w], DISCRETE, LINEAR]
DefineSystem[FIR[n, coeffs], DISCRETE, LINEAR, SHIFTINVARIANT]
DefineSystem[FT[t, w], CONTINUOUS, LINEAR]
DefineSystem[IIR[n, coeffs], DISCRETE, LINEAR, SHIFTINVARIANT]
DefineSystem[Im,
	     ADDITIVE, CONTINUOUS, DISCRETE, MEMORYLESS, SHIFTINVARIANT]
DefineSystem[Interleave[n], DISCRETE]
DefineSystem[InvDFT[l, k, n], DISCRETE, LINEAR]
DefineSystem[InvDTFT[w, n], DISCRETE, LINEAR]
DefineSystem[InvFT[w, t], CONTINUOUS, LINEAR]
DefineSystem[InvL[s, t], CONTINUOUS, LINEAR]
DefineSystem[InvZ[z, n], DISCRETE, LINEAR]
DefineSystem[L[t, s], CONTINUOUS, LINEAR]
DefineSystem[Periodic[k, n], CONTINUOUS, DISCRETE, SEPARABLE]
DefineSystem[PolyphaseDownsample[m, h, n], DISCRETE, LINEAR]
DefineSystem[PolyphaseUpsample[l, h, n], DISCRETE, LINEAR]
DefineSystem[Re,
	     ADDITIVE, CONTINUOUS, DISCRETE, MEMORYLESS, SHIFTINVARIANT]
(* DefineSystem[Reciprocal, CONTINUOUS, DISCRETE, MEMORYLESS, SHIFTINVARIANT] *)
DefineSystem[Rev[n], CONTINUOUS, DISCRETE, LINEAR, SEPARABLE]
DefineSystem[ScaleAxis[l, w], CONTINUOUS, LINEAR, SEPARABLE]
DefineSystem[Shift[l, n],
	     CONTINUOUS, DISCRETE, LINEAR, SEPARABLE, SHIFTINVARIANT]
DefineSystem[Subtract, CONTINUOUS, DISCRETE, MEMORYLESS]
DefineSystem[Summation[i, ibeg, iend, inc], DISCRETE, LINEAR]
DefineSystem[Upsample[l, n], DISCRETE, LINEAR]
Upsample/: SEPARABLE[ Upsample[m_, n_List] ] := DiagonalMatrixQ[m]
DefineSystem[Z[n, z], DISCRETE, LINEAR]

(*  Retrive the properties attached to an operator  *)

SetAttributes[SystemProperties, Listable]

SystemProperties[op_] := ( op -> Select[ SPproperties, TrueQ[#1[op]]& ] )


(*  E N D     P A C K A G E  *)

End[]
EndPackage[]

If [ TrueQ[ $VersionNumber >= 2.0 ],
     On[ General::spell ];
     On[ General::spell1 ] ];


(*  W R I T E     P R O T E C T I O N  *)

Block [	{newfuns},
	newfuns = { DefineSystem,	MoveOperatorsToFront,	SystemQ,
		    SystemProperties,	ValidOperatorQ };
	Combine[ SPfunctions, newfuns ];
	Apply[ Protect, newfuns ] ]


(*  E N D I N G     M E S S A G E  *)

Print["System definitions have been loaded."]
Null
